/*
 * Sylvain BERTRAND <digital.ragnarok@gmail.com>
 * code protected by GNU GPL v3
 */
#include <X11/Xlib.h>
#include <X11/extensions/sync.h>
#include <glib.h>
#include <libxml/xinclude.h>
#include "../nls.h"
#include "../toolkit/display.h"
#include "../toolkit/xml.h"
#include "misc.h"
#include "geom.h"
#include "frame.h"
#include "window.h"
#include "stacking.h"
#include "client.h"
#include "grab.h"
#include "actions.h"
#include "event.h"
#include "config.h"
#include "place.h"
#include "moveresize.h"
#include "translate.h"
struct mouse_binding {
	guint state;
	guint button;
	GSList *actions[WM_NUM_MOUSE_ACTIONS];
};
static GSList *bound_contexts[WM_FRAME_NUM_CONTEXTS];
static gboolean replay_pointer_needed;
enum wm_frame_context mouse_button_frame_context(enum wm_frame_context context, guint button, guint state)
{
	GSList *it;
	enum wm_frame_context x = context;
	for(it = bound_contexts[context]; it; it = g_slist_next(it)) {
		struct mouse_binding *b = it->data;
		if(b->button == button && b->state == state)
			return context;
	}
	switch (context) {
	case WM_FRAME_CONTEXT_NONE:
	case WM_FRAME_CONTEXT_DESKTOP:
	case WM_FRAME_CONTEXT_CLIENT:
	case WM_FRAME_CONTEXT_FRAME:
	case WM_FRAME_CONTEXT_MOVE_RESIZE:
		break;
	case WM_FRAME_CONTEXT_ROOT:
		x = WM_FRAME_CONTEXT_DESKTOP;
		break;
	case WM_FRAME_NUM_CONTEXTS:
		g_assert_not_reached();
	}
	if(x != context)
		return mouse_button_frame_context(x, button, state);
	else
		return x;
}

void mouse_grab_for_client(struct wm_client *client, gboolean grab)
{
	gint i;
	GSList *it;
	for(i = 0; i < WM_FRAME_NUM_CONTEXTS; ++i)
		for(it = bound_contexts[i]; it; it = g_slist_next(it)) {
			struct mouse_binding *b = it->data;
			Window win;
			gint mode;
			guint mask;
			if(FRAME_CONTEXT(i, client)) {
				win = client->frame->window;
				mode = GrabModeAsync;
				mask = ButtonPressMask | ButtonMotionMask | ButtonReleaseMask;
			} else if(CLIENT_CONTEXT(i, client)) {
				win = client->window;
				mode = GrabModeSync;
				mask = ButtonPressMask;
			} else
				continue;
			if(grab)
				grab_button_full(b->button, b->state, win, mask, mode, WM_CURSOR_NONE);
			else
				ungrab_button(b->button, b->state, win);
		}
}

static void grab_all_clients(gboolean grab)
{
	GList *it;
	for(it = client_list; it; it = g_list_next(it))
		mouse_grab_for_client(it->data, grab);
}

void mouse_unbind_all(void)
{
	gint i;
	GSList *it;
	for(i = 0; i < WM_FRAME_NUM_CONTEXTS; ++i) {
		for(it = bound_contexts[i]; it; it = g_slist_next(it)) {
			struct mouse_binding *b = it->data;
			gint j;
			for(j = 0; j < WM_NUM_MOUSE_ACTIONS; ++j) {
				GSList *jt;
				for(jt = b->actions[j]; jt; jt = g_slist_next(jt))
					actions_act_unref(jt->data);
				g_slist_free(b->actions[j]);
			}
			g_slice_free(struct mouse_binding, b);
		} g_slist_free(bound_contexts[i]);
		bound_contexts[i] = NULL;
	}
}

static enum wm_user_action mouse_action_to_user_action(enum wm_mouse_action a)
{
	switch (a) {
	case WM_MOUSE_ACTION_PRESS:
		return WM_USER_ACTION_MOUSE_PRESS;
	case WM_MOUSE_ACTION_RELEASE:
		return WM_USER_ACTION_MOUSE_RELEASE;
	case WM_MOUSE_ACTION_CLICK:
		return WM_USER_ACTION_MOUSE_CLICK;
	case WM_MOUSE_ACTION_DOUBLE_CLICK:
		return WM_USER_ACTION_MOUSE_DOUBLE_CLICK;
	case WM_MOUSE_ACTION_MOTION:
		return WM_USER_ACTION_MOUSE_MOTION;
	default:
		g_assert_not_reached();
	}
}

static gboolean fire_binding(enum wm_mouse_action a, enum wm_frame_context context, struct wm_client *c,
	guint state, guint button, gint x, gint y)
{
	GSList *it;
	struct mouse_binding *b;
	for(it = bound_contexts[context]; it; it = g_slist_next(it)) {
		b = it->data;
		if(b->state == state && b->button == button)
			break;
	}
	if(it == NULL)
		return FALSE;
	actions_run_acts(b->actions[a], mouse_action_to_user_action(a), state, x, y, button, context, c);
	return TRUE;
}

void mouse_replay_pointer(void)
{
	if(replay_pointer_needed) {
		XAllowEvents(t_display, ReplayPointer, event_time());
		replay_pointer_needed = FALSE;
	}
}

gboolean mouse_event(struct wm_client *client, XEvent *e)
{
	static Time ltime;
	static guint button = 0, state = 0, lbutton = 0;
	static Window lwindow = None;
	static gint px, py, pwx = -1, pwy = -1;
	gboolean used = FALSE;
	enum wm_frame_context context;
	gboolean click = FALSE;
	gboolean dclick = FALSE;
	switch (e->type) {
	case ButtonPress:
		context = frame_context(client, e->xbutton.window, e->xbutton.x, e->xbutton.y);
		context = mouse_button_frame_context(context, e->xbutton.button, e->xbutton.state);
		px = e->xbutton.x_root;
		py = e->xbutton.y_root;
		if(!button)
			pwx = e->xbutton.x;
		if(!button)
			pwy = e->xbutton.y;
		button = e->xbutton.button;
		state = e->xbutton.state;
		if(CLIENT_CONTEXT(context, client))
			replay_pointer_needed = TRUE;
		used =
			fire_binding(WM_MOUSE_ACTION_PRESS, context, client, e->xbutton.state, e->xbutton.button, e->xbutton.x_root,
			e->xbutton.y_root) || used;
		if(grab_on_pointer())
			button = 0;
		mouse_replay_pointer();
		if(!CLIENT_CONTEXT(context, client))
			break;
	case ButtonRelease:
		context = frame_context(client, e->xbutton.window, pwx, pwy);
		context = mouse_button_frame_context(context, e->xbutton.button, e->xbutton.state);
		if(e->xbutton.button == button)
			pwx = pwy = -1;
		if(e->xbutton.button == button) {
			gint junk1, junk2;
			Window wjunk;
			guint ujunk, b, w, h;
			t_display_ignore_errors(TRUE);
			junk1 = XGetGeometry(t_display, e->xbutton.window, &wjunk, &junk1, &junk2, &w, &h, &b, &ujunk);
			t_display_ignore_errors(FALSE);
			if(junk1) {
				if(e->xbutton.x >= (signed)-b && e->xbutton.y >= (signed)-b && e->xbutton.x < (signed)(w + b)
					&& e->xbutton.y < (signed)(h + b)) {
					click = TRUE;
					if(lbutton == button && lwindow == e->xbutton.window && e->xbutton.time - config_mouse_dclicktime <= ltime) {
						dclick = TRUE;
						lbutton = 0;
					} else {
						lbutton = button;
						lwindow = e->xbutton.window;
					}
				} else {
					lbutton = 0;
					lwindow = None;
				}
			}
			button = 0;
			state = 0;
			ltime = e->xbutton.time;
		}
		used =
			fire_binding(WM_MOUSE_ACTION_RELEASE, context, client, e->xbutton.state, e->xbutton.button, e->xbutton.x_root,
			e->xbutton.y_root) || used;
		if(click)
			used =
				fire_binding(WM_MOUSE_ACTION_CLICK, context, client, e->xbutton.state, e->xbutton.button, e->xbutton.x_root,
				e->xbutton.y_root) || used;
		if(dclick)
			used =
				fire_binding(WM_MOUSE_ACTION_DOUBLE_CLICK, context, client, e->xbutton.state, e->xbutton.button,
				e->xbutton.x_root, e->xbutton.y_root) || used;
		break;
	case MotionNotify:
		if(button) {
			context = frame_context(client, e->xmotion.window, pwx, pwy);
			context = mouse_button_frame_context(context, button, state);
			if(ABS(e->xmotion.x_root - px) >= config_mouse_threshold || ABS(e->xmotion.y_root - py) >= config_mouse_threshold) {
				used = fire_binding(WM_MOUSE_ACTION_MOTION, context, client, state, button, px, py);
				button = 0;
				state = 0;
			}
		}
		break;
	default:
		g_assert_not_reached();
	}
	return used;
}

gboolean mouse_bind(const gchar *buttonstr, enum wm_frame_context context, enum wm_mouse_action mact,
	struct wm_actions_act *action)
{
	guint state, button;
	struct mouse_binding *b;
	GSList *it;
	g_assert(context != WM_FRAME_CONTEXT_NONE);
	if(!translate_button(buttonstr, &state, &button)) {
		g_message(_("Invalid button \"%s\" in mouse binding"), buttonstr);
		return FALSE;
	}
	for(it = bound_contexts[context]; it; it = g_slist_next(it)) {
		b = it->data;
		if(b->state == state && b->button == button) {
			b->actions[mact] = g_slist_append(b->actions[mact], action);
			return TRUE;
		}
	}
	b = g_slice_new0(struct mouse_binding);
	b->state = state;
	b->button = button;
	b->actions[mact] = g_slist_append(NULL, action);
	bound_contexts[context] = g_slist_append(bound_contexts[context], b);
	return TRUE;
}

void mouse_startup(void)
{
	grab_all_clients(TRUE);
}

void mouse_shutdown(void)
{
	grab_all_clients(FALSE);
	mouse_unbind_all();
}
